package cn.johnnyjian.getris.game

import cn.johnnyjian.getris.game.quadrel.QuadrelTwo
import groovy.mock.interceptor.MockFor
import org.junit.Assert
import org.junit.Before
import org.junit.Test

class ContainerTest extends GroovyTestCase {

	def container

	@Before void setUp() {
		container = new Container(10, 20, 5)
	}

	@Test void testConstructor() {
		assertEquals 10, container.width
		assertEquals 20, container.height
		assertEquals 5, container.queueSize

		assertEquals 5, container.queue.size()
		container.queue.each {
			assertTrue it instanceof Quadrel
		}

		assertTrue container.squares instanceof boolean[][]
		assertEquals 20, container.squares.length
		assertEquals 10, container.squares[0].length

		assertSame container, container.droppingQuadrel.delegate

		assertFalse container.gameOver
	}

	@Test void testDroppingQuadrel() {
		final containerMethods = ['moveLeft', 'moveRight', 'rotate', 'dropPace', 'dropDown']
		def droppingQuadrel = container.droppingQuadrel

		def mockContainer = new MockFor(Container)
		containerMethods.each { name ->
			mockContainer.demand."${name}Quadrel" { true }
		}

		mockContainer.use(container) {
			containerMethods.each { name ->
				assertTrue droppingQuadrel."$name"()
			}
			shouldFail(MissingMethodException) {
				droppingQuadrel.missingMethod()
			}
		}
	}

	@Test void testDroppingQuadrelProperties() {
		def droppingQuadrel = container.droppingQuadrel
		container.quadrel = new MockQuadrel()
		droppingQuadrel.x = 2
		droppingQuadrel.y = 13

		assertEquals 3, droppingQuadrel.left
		assertEquals 4, droppingQuadrel.right
		assertEquals 13, droppingQuadrel.top
		assertEquals 15, droppingQuadrel.bottom
	}

	@Test void testDroppingQuadrelEachSquare() {
		def droppingQuadrel = container.droppingQuadrel
		container.quadrel = new MockQuadrel()
		droppingQuadrel.x = 2
		droppingQuadrel.y = 13

		def points = [
		    [4, 13], [3, 14], [4, 14], [4, 15]
		]
		def i = 0
		droppingQuadrel.eachSquare { x, y ->
			assertEquals points[i++], [x, y]
		}
	}

	@Test void testDroppingQuadrelIsSquareAt() {
		def droppingQuadrel = container.droppingQuadrel
		container.quadrel = new MockQuadrel()
		droppingQuadrel.x = 2
		droppingQuadrel.y = 13

		[[4, 13], [3, 14], [4, 14], [4, 15]].each { point ->
			assertTrue droppingQuadrel.isSquareAt(point[0], point[1])
		}
	}

	@Test void testStartToDrop() {
		container.queue[0] = new MockQuadrel()

		def quadrel = container.queue.peek()
		def oldQueue = container.queue.clone()

		def observed = false
		container.addObserver({ o, arg -> observed = true } as Observer)

		container.startToDrop()

		assert observed
		assertSame quadrel, container.quadrel
		assertEquals 5, container.queue.size()
		oldQueue[1..4].eachWithIndex { expected, i ->
			assertSame expected, container.queue[i]
		}
		assertEquals 0, container.droppingQuadrel.top
		assertEquals 4, container.droppingQuadrel.left

		def points = [] as Set
		container.droppingQuadrel.eachSquare { x, y ->
			assertTrue container.isSquareAt(x, y)
			points.add([x, y])
		}
		container.squares.eachWithIndex { row, y ->
			row.eachWithIndex { square, x ->
				if (!([x, y] in points)) {
					assertFalse square
				}
			}
		}
	}

	@Test void testSquareAt() {
		container.setSquareAt 1, 10, true
		assertTrue container.isSquareAt(1, 10)

		container.setSquareAt 1, 10, false
		assertFalse container.isSquareAt(1, 10)

		assertFalse container.isSquareAt(2, -1)
		assertTrue container.isSquareAt(-1, -1)
		assertTrue container.isSquareAt(10, 3)
		assertTrue container.isSquareAt(-1, 10)
		assertTrue container.isSquareAt(3, 20)
	}

	@Test void testDropPaceQuadrel() {
		container.queue.clear()
		container.queue.addAll((1..5).collect { new MockHeightSevenQuadrel() })

		def observed
		container.addObserver({ o, arg -> observed = true } as Observer)
		[13, 7, 1].each { pace ->
			container.dropNextQuadrel()
			assertFalse container.gameOver

			pace.times {
				observed = false
				container.dropPaceQuadrel()
				assert observed
				assertFalse container.droppingQuadrel.reachedBottom
			}

			observed = false
			container.dropPaceQuadrel()
			assert !observed
			assertTrue container.droppingQuadrel.reachedBottom
		}

		container.dropNextQuadrel()
		assertTrue container.gameOver
	}

	@Test void testDropPaceQuadrel2() {
		container.queue.clear()
		container.queue.addAll((1..5).collect { new MockHeightFiveQuadrel() })

		[15, 10, 5, 0].each { pace ->
			container.dropNextQuadrel()
			assertFalse container.gameOver

			pace.times {
				container.dropPaceQuadrel()
				assertFalse container.droppingQuadrel.reachedBottom
			}

			container.dropPaceQuadrel()
			assertTrue container.droppingQuadrel.reachedBottom
		}

		container.dropNextQuadrel()
		assertTrue container.gameOver
	}

	@Test void testDropDownQuadrel() {
		container.queue.clear()
		container.queue.addAll((1..5).collect { new MockHeightSevenQuadrel() })

		def observed
		container.addObserver({ o, arg -> observed = true } as Observer)
		[13, 7].each { yb ->
			container.dropNextQuadrel()

			observed = false
			container.dropDownQuadrel()
			assert observed

			assertTrue container.droppingQuadrel.reachedBottom
			assertEquals 3, container.droppingQuadrel.x
			assertEquals yb, container.droppingQuadrel.y
			container.droppingQuadrel.eachSquare { x, y ->
				assertTrue container.isSquareAt(x, y)
			}
		}

		container.dropNextQuadrel()
		container.dropPaceQuadrel()

		observed = false
		container.dropDownQuadrel()
		assert !observed
		assertTrue container.droppingQuadrel.reachedBottom
		assert 1, container.droppingQuadrel.y
	}

	@Test void testMoveLeftQuadrel() {
		container.queue[0] = new MockQuadrel()
		container.startToDrop()

		def observed
		container.addObserver({ o, arg -> observed = true } as Observer)
		[3, 2, 1, 0].each { expected ->
			observed = false
			container.moveLeftQuadrel()
			assert observed
			assertEquals expected, container.droppingQuadrel.left
		}

		observed = false
		container.moveLeftQuadrel()
		assert !observed
		assertEquals 0, container.droppingQuadrel.left
	}

	@Test void testMoveRightQuadrel() {
		container.queue[0] = new MockQuadrel()
		container.startToDrop()

		def observed
		container.addObserver({ o, arg -> observed = true } as Observer)
		[5, 6, 7, 8].each { expected ->
			observed = false
			container.moveRightQuadrel()
			assert observed
			assertEquals expected, container.droppingQuadrel.left
		}

		observed = false
		container.moveRightQuadrel()
		assert !observed
		assertEquals 8, container.droppingQuadrel.left
	}

	@Test void testRotateQuadrel() {
		container.queue[0] = new QuadrelTwo()
		container.startToDrop()

		container.dropPaceQuadrel()
		assertEquals 1, container.quadrel.height
		def observed = false
		container.addObserver({ o, arg -> observed = true } as Observer)
		container.rotateQuadrel()
		assert observed
		assertEquals 1, container.quadrel.width

		4.times { container.moveLeftQuadrel() }
		assertEquals 1, container.droppingQuadrel.left
		observed = false
		container.rotateQuadrel()
		assert !observed
		assertEquals 1, container.droppingQuadrel.width

		8.times { container.moveRightQuadrel() }
		assertEquals 9, container.droppingQuadrel.left
		container.rotateQuadrel()
		assertEquals 1, container.droppingQuadrel.width
	}

	@Test void testRotateQuadrel2() {
		container.queue[0] = new QuadrelTwo()
		container.startToDrop()

		assertEquals 1, container.quadrel.height
		container.rotateQuadrel()
		assertEquals 1, container.quadrel.height

		18.times { container.dropPaceQuadrel() }
		assertEquals 18, container.droppingQuadrel.top
		container.rotateQuadrel()
		assertEquals 1, container.quadrel.height
	}

	@Test void testRemoveFullLines() {
		def init = [
		    [0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
			[0, 1, 0, 1, 0, 0, 0, 1, 1, 1],
			[1, 1, 1, 1, 1, 1, 0, 1, 0, 0],
			[1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
			[1, 0, 1, 1, 1, 1, 0, 1, 1, 1],
			[1, 1, 1, 1, 1, 1, 0, 1, 1, 1]
		] as boolean[][]
		init.eachWithIndex { row, i ->
			container.squares[i + 14] = row.clone()
		}
		container.squares[0][0] = true

		container.queue[0] = new QuadrelTwo()
		container.startToDrop()
		container.dropPaceQuadrel()
		container.rotateQuadrel()
		container.moveQuadrelWith {
			container.droppingQuadrel.x = 4
			container.droppingQuadrel.y = 16
		}

		def observed = false
		container.addObserver({ o, arg -> observed = true } as Observer)

		container.removeFullLines()

		assert observed
		assertEquals 2, container.removedLineCount
		init[2][6] = true
		init[4][6] = true
		init[0, 1, 2, 4].eachWithIndex { row, i ->
			assertArrayEquals row as Object[], container.squares[i + 16] as Object[]
		}
		container.squares[(0..15) - 2].each {
			assertArrayEquals(([false] * 10) as Object[], it as Object[])
		}
		assertArrayEquals(([true] + [false] * 9) as Object[], container.squares[2] as Object[])
	}

	@Test void testRemoveFullLines2() {
		def init = [
			[1, 1, 1, 1, 1, 1, 0, 1, 0, 0],
			[1, 1, 1, 1, 1, 1, 0, 1, 1, 0],
			[1, 0, 1, 1, 1, 1, 0, 1, 1, 1],
			[0, 1, 1, 1, 1, 1, 0, 1, 1, 1]
		] as boolean[][]
		init.eachWithIndex { row, i ->
			container.squares[i + 16] = row.clone()
		}

		container.queue[0] = new QuadrelTwo()
		container.startToDrop()
		container.dropPaceQuadrel()
		container.rotateQuadrel()
		container.moveQuadrelWith {
			container.droppingQuadrel.x = 4
			container.droppingQuadrel.y = 16
		}

		def observed = false
		container.addObserver({ o, arg -> observed = true } as Observer)

		container.removeFullLines()

		assert !observed
		assertEquals 0, container.removedLineCount
		init.eachWithIndex { row, i ->
			row[6] = true
			assertArrayEquals row as Object[], container.squares[i + 16] as Object[]
		}
		container.squares[0..15].each {
			assertArrayEquals(([false] * 10) as Object[], it as Object[])
		}
	}

	@Test void testMoveLine() {
		container.squares[0] = ([false] * 10) as boolean[]
		container.squares[19] = ([true] * 10) as boolean[]

		container.moveLine 0, 19

		assertArrayEquals(([false] * 10) as Object[], container.squares[19])
	}

	@Test void testMoveLine2() {
		container.squares[19] = ([true] * 10) as boolean[]

		container.moveLine(-1, 19)

		assertArrayEquals(([false] * 10) as Object[], container.squares[19])
	}

}

class MockHeightSevenQuadrel extends Quadrel {

	def squares = [
		[0, 1, 0],
		[1, 1, 0],
		[0, 1, 1],
		[1, 1, 0],
		[0, 1, 1],
		[1, 1, 1],
		[1, 0, 1]
	] as boolean[][]
	def bound = 7
	def offset = [left: 0, top: 0]

}

class MockHeightFiveQuadrel extends Quadrel {

	def squares = [
		[0, 1, 0],
		[1, 1, 0],
		[0, 1, 1],
		[1, 1, 0],
		[0, 1, 1],
	] as boolean[][]
	def bound = 5
	def offset = [left: 0, top: 0]

}
